<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>watch的实现原理</title>
	</head>
	<body>
		<script>
			let activeEffect

      const effectStack = []

			function effect(fn,options = {}) {

        const effectFn = () => {
          cleanup(effectFn)
          activeEffect = effectFn
          effectStack.push(effectFn)
          const res = fn()
          effectStack.pop()

          activeEffect = effectStack[effectStack.length - 1]

          return res 
        }


        effectFn.options = options
        effectFn.deps = []

        if(!effectFn.options.lazy){
          effectFn()
        }

        return effectFn
			}

      function cleanup (effectFn){
        for(let i=0;i<effectFn.deps.length;i++){
          const deps = effectFn.deps[i]
          deps.delete(effectFn)
        }

        effectFn.deps.length = 0
      }

			const bucket = new WeakMap()

			const track = (target, key) => {
        console.log('%c [ key ]-36', 'font-size:13px; background:pink; color:#bf2c9f;', key);
				if (!activeEffect) {
					return target[key]
				}

				// 1. 获取当前对象对应的依赖集合
				let depsMap = bucket.get(target)
        
				if (!depsMap) {
          depsMap = new Map()
					bucket.set(target, depsMap)
				}

				// 2. 获取当前属性对应的依赖集合
				let deps = depsMap.get(key)
        
				if (!deps) {
          deps = new Set()
					depsMap.set(key, deps)
				}
        
				deps.add(activeEffect)

        activeEffect.deps.push(deps)

			}

			const trigger = (target, key) => {
				// 1. 获取当前对象对应的依赖集合
				const depsMap = bucket.get(target)
				if (!depsMap) return

				// 2. 获取当前属性对应的依赖集合
				const effects = depsMap.get(key)

				// 3. 执行依赖函数
        const effectsToRun = new Set()
        effects && effects.forEach((fn) => {
          if(activeEffect !== fn){ // 避免自己执行自己 无限递归爆栈
            effectsToRun.add(fn)
          }
        })

        effectsToRun && effectsToRun.forEach((effectFn) =>{
          // 如果存在调度器 则调用调度器 把函数作为参数传入
          if(effectFn.options.scheduler){
            effectFn.options.scheduler(effectFn)
          }else{
            effectFn()
          }
        })
			}

      // ********************
      const jobQueue = new Set ()

      const p = Promise.resolve()

      let isFlushing = false

      function flushJob (){
        if(isFlushing)return 
        isFlushing = true
        
        p.then(()=>{
          jobQueue.forEach(job=>job())
        }).finally(()=>{
          isFlushing = false
        })

      }

      function computed (getter){
        let value
        let dirty = true

        const effectFn =effect(getter,{
          scheduler(){
            dirty=true
            trigger(obj,'value')
          },
          lazy:true
        })


        const obj= {
          get value(){
            if(dirty){
              value = effectFn()
              dirty = false
            }
            track(obj,'value')
            return value
          }
        }

        return obj
      }


      function watch(source,cb){
        let getter 
        if(typeof source === 'function'){
          getter = source
        }else{
          getter = () => traverse(source)
        }

        let oldValue,newValue

        const effectFn = effect(()=>getter(),{
          lazy:true,
          scheduler(){
            newValue = effectFn()
            cb(newValue,oldValue)
            oldValue = newValue
          }
        })

        oldValue = effectFn()
      }

      function traverse(value, seen= new Set()){
        if(typeof value !=='object' || value ===null || seen.has(value)) return 

        seen.add(value)

        for( const k  in value ){
          traverse(value[k],seen)
        }

        return value 
      }


      // ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


			// const data = {ok:true, text: 'hello world' }
      const data = {foo:1,bar:2}

			const obj = new Proxy(data, {
				get(target, key) {
					track(target, key)

					return target[key]
				},

				set(target, key, newVal) {
					target[key] = newVal
					trigger(target, key)
				},
			})


      watch (()=>obj.bar,()=>{

        console.log('%c [  ]-183', 'font-size:13px; background:pink; color:#bf2c9f;', 'change change !');
      })

      obj.foo++
      obj.bar++
      obj.foo++

	
		</script>
	</body>
</html>
